#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/cdefs.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include "fstool_common.h"
#include "fstool_io.h"
#include "fsck_fragment.h"
#include "fsck_cache.h"
#include "fsck_boot.h"
#include "fsck_fat.h"
#include "fsck_dir.h"
#include "hi_fstool.h"

#define FAT_COMPARE_MAX_KB  (4096)

static volatile const char FSTOOL_VERSIONINFO[] = "HIBERRY FSTOOL MW Version: " MW_VERSION;
static volatile const char FSTOOL_BUILD_DATE[] = "HIBERRY FSTOOL Build Date:" __DATE__ " Time:" __TIME__;

static int FSTOOL_IsFatOverLarge(FSCK_BOOT_SECTOR_S* pstBoot)
{
    return (((size_t)pstBoot->BPB_FATSz32 * pstBoot->BPB_BytesPerSec / FSTOOL_COMM_KB_2_BYTE_FACTOR) > FAT_COMPARE_MAX_KB) ? 1:0;
}

static int FSTOOL_IsFSDirty(FSCK_BOOT_SECTOR_S* pstBoot, u_char* fat, int* pIsBootDirty, int* pIsFatDirty)
{
    if ((*pIsBootDirty = FSCK_BOOT_IsDirty(pstBoot)) || (*pIsFatDirty = FSCK_FAT_IsDirty(pstBoot, fat)))
    {
        FSTOOL_COMM_LOG_INFO("File system is dirty !\n");
        return 1;
    }

    FSTOOL_COMM_LOG_WARN("File system is clean !\n");
    return 0;
}

static int FSTOOL_WhetherCheckDir(HI_FSTOOL_CHECK_MODE_E enMode, int isFSDirty)
{
    if ((HI_FSTOOL_CHECK_MODE_ALWAYS == enMode) || (HI_FSTOOL_CHECK_MODE_ALWAYS_MAIN_FAT == enMode) || isFSDirty)
    {
        return 1;
    }

    return 0;
}

static int FSTOOL_WhetherCmpFAT(FSCK_BOOT_SECTOR_S* pstBoot, HI_FSTOOL_CHECK_MODE_E enMode, int isFSDirty)
{
    if (FSTOOL_IsFatOverLarge(pstBoot) || (0 <= pstBoot->BPB_ExtFlags))
    {
        return 0;
    }

    if ((HI_FSTOOL_CHECK_MODE_ALWAYS_MAIN_FAT == enMode) || (HI_FSTOOL_CHECK_MODE_DIRTY_MAIN_FAT == enMode))
    {
        return 0;
    }

    if (!isFSDirty && (HI_FSTOOL_CHECK_MODE_DIRTY == enMode))
    {
        return 0;
    }

    return 1;
}

static u_int FSTOOL_ConvertErrCode(u_int errcode)
{
    if (errcode & FSCK_READERR)
    {
        return HI_ERR_FSTOOL_READ_FAILURE;
    }
    else if (errcode & FSCK_WRITERR)
    {
        return HI_ERR_FSTOOL_WRITE_FAILURE;
    }
    else if (errcode & FSCK_OOM)
    {
        return HI_ERR_FSTOOL_NOT_ENOUGH_MEMORY;
    }
    else if ((errcode & FSCK_FSBOOTERR) || (errcode & FSCK_FSFATAL))
    {
        return HI_ERR_FSTOOL_FS_EXCEPTION;
    }
    else if (errcode & FSCK_FS_NOTSUPPORT)
    {
        return HI_ERR_FSTOOL_FSTYPE_UNSUPPORT;
    }
    else
    {
        if (errcode & FSCK_FSFRAG)
        {
            return HI_ERR_FSTOOL_FRAGMENT_SEVERELY;
        }
        else
        {
            if (errcode & (FSCK_FSFATMOD|FSCK_FSDIRMOD))
            {
                PRINTK("\n***** FILE SYSTEM WAS MODIFIED *****\n");
            }
            PRINTK("***** FILE SYSTEM FSCK SUCCESS *****\n");

            return HI_FSTOOL_SUCCESS;
        }
    }
}

u_int HI_FSTOOL_Check(const char* pszPath, unsigned int u32FragmentThr, HI_FSTOOL_CHECK_MODE_E enMode)
{
    int fd = -1;
    FSCK_BOOT_SECTOR_S stBoot = {0};
    u_char* fat = NULL;
    u_char* fat2 = NULL;
    cl_t lastAllocCl = 0;
    int isBootDirty = 0;
    int isFatDirty = 0;
    int isFSDirty = 0;
    int needcleandirsection=0;
    u_int ret =0;
    int i=0;
    u_char* boot_sector = NULL;
    u_char* fsinfo_sector = NULL;

    FSTOOL_COMM_LOG_INFO("%s, %s\n",FSTOOL_VERSIONINFO, FSTOOL_BUILD_DATE);

    if (NULL == pszPath)
    {
        FSTOOL_COMM_LOG_ERROR("pszPath is NULL, and ret:%d !\n", HI_ERR_FSTOOL_ILLEGAL_PARAM);
        return HI_ERR_FSTOOL_ILLEGAL_PARAM;
    }

    if (HI_FSTOOL_CHECK_MODE_BUTT <= enMode)
    {
        FSTOOL_COMM_LOG_ERROR("enMode(%d) is not supprt, and ret:%d !\n", enMode, HI_ERR_FSTOOL_ILLEGAL_PARAM);
        return HI_ERR_FSTOOL_ILLEGAL_PARAM;
    }

    fd = FSTOOL_IO_Open(pszPath);
    if (fd < 0)
    {
        FSTOOL_COMM_LOG_ERROR("FSTOOL Check open error, path=%s\n", pszPath);
        return HI_ERR_FSTOOL_OPEN_FAILURE;
    }

    ret = FSCK_BOOT_Read(&stBoot, fd, &boot_sector, &fsinfo_sector);

    if (FSCK_FSOK != ret)
    {
        if(boot_sector != NULL)
        {
            free(boot_sector);
        }
        if(fsinfo_sector != NULL)
        {
            free(fsinfo_sector);
        }
        FSTOOL_IO_Close(fd);
        FSTOOL_COMM_LOG_ERROR("read stBoot info error ! \n");
        return FSTOOL_ConvertErrCode(ret);
    }

   /**** FAT > FAT_COMPARE_MAX_KB size then skip comparing, in the future we need to just re-write
      this for less memory***/

    ret =FSCK_FAT_Read(fd, &stBoot, stBoot.BPB_ExtFlags, &fat);
    if (FSCK_FSOK != ret)
    {
        FSTOOL_COMM_LOG_ERROR("error FSCK_FAT_Read !\n");
        goto out;
    }

    isFSDirty = FSTOOL_IsFSDirty(&stBoot, fat, &isBootDirty, &isFatDirty);
    if (FSTOOL_WhetherCmpFAT(&stBoot, enMode, isFSDirty))
    {
        FSTOOL_COMM_LOG_DEBUG("Compare fat!\n");
        for (i = 1; i < (int)stBoot.BPB_NumFATs; i++)
        {
            ret = FSCK_FAT_Read(fd, &stBoot,i,&fat2);
            if (FSCK_FSOK != ret)
            {
                FSTOOL_COMM_LOG_ERROR("Fatal error readfat for comparison !\n");
                goto out;
            }
            ret |= FSCK_FAT_Cmp(&stBoot, fat, fat2, i);
            free(fat2);
            fat2 = NULL;
            if (ret & FSCK_FSFATAL)
            {
                FSTOOL_COMM_LOG_ERROR("Fatal error during FAT comparison\n");
                goto out;
            }
        }
    }

    FSTOOL_COMM_LOG_DEBUG("Check Cluster Chains  !\n");
    ret |= FSCK_FAT_Check(&stBoot, stBoot.BPB_ExtFlags, fat);
    if (ret & FSCK_FSFATAL)
    {
        FSTOOL_COMM_LOG_ERROR("error during  FSCK_FAT_Check()\n");
        goto out;
    }

    if (FSTOOL_WhetherCheckDir(enMode, isFSDirty))
    {
        FSTOOL_COMM_LOG_DEBUG("** Checking Directories ! \n");
        needcleandirsection = 1;
        ret |= FSCK_DIR_Reset(&stBoot);
        if (ret & FSCK_FSFATAL)
        {
            FSTOOL_COMM_LOG_ERROR("error during FSCK_DIR_Reset()! \n");
            goto out;
        }

        ret |= FSCK_DIR_HandleTree(fd, &stBoot, enMode);
        if (ret & FSCK_FSFATAL)
        {
            FSTOOL_COMM_LOG_ERROR("error during FSCK_DIR_HandleTree()! \n");
            goto out;
        }

        FSTOOL_COMM_LOG_DEBUG("**Checking for Lost Files !\n");
        ret |= FSCK_FAT_CheckLost(&stBoot);
        if (ret & FSCK_FSFATAL)
        {
            FSTOOL_COMM_LOG_ERROR("error during FSCK_FAT_CheckLost! \n");
            goto out;
        }

        ret |= FSCK_FAT_GetLastAlloCl(&stBoot, &lastAllocCl);
        if (ret & FSCK_FSFATAL)
        {
            FSTOOL_COMM_LOG_ERROR("error during FSCK_FAT_GetLastAlloCl! \n");
            goto out;
        }

        FSTOOL_COMM_LOG_DEBUG("**Update FSInfo !\n");
        ret |= FSCK_BOOT_UpdateFSInfo(&stBoot, fd, lastAllocCl, fsinfo_sector);
        if (ret & FSCK_FSFATAL)
        {
            FSTOOL_COMM_LOG_ERROR("error during FSCK_BOOT_UpdateFSInfo! \n");
            goto out;
        }

        /* now write the FATs */
        if (ret & FSCK_FSFATMOD)
        {
            FSTOOL_COMM_LOG_DEBUG("**Repair FAT !\n");
            ret |= FSCK_FAT_Write(fd, &stBoot);
            if (ret & FSCK_FSFATAL)
            {
                FSTOOL_COMM_LOG_ERROR("error during FSCK_FAT_Write\n");
                goto out;
            }
        }
        else
        {
            if (isFatDirty)
            {
                FSTOOL_COMM_LOG_INFO("Clean FAT dirty flag\n");
                ret |= FSCK_FAT_CleanDirtyFlag(&stBoot, fd);
                if (ret & FSCK_FSFATAL)
                {
                    FSTOOL_COMM_LOG_ERROR("error during FSCK_FAT_CleanDirtyFlag\n");
                    goto out;
                }
            }
        }

        if (isBootDirty)
        {
            ret |= FSCK_BOOT_CleanDirtyFlag(fd);
            if (ret & FSCK_FSFATAL)
            {
                FSTOOL_COMM_LOG_ERROR("error during FSCK_BOOT_CleanDirtyFlag\n");
                goto out;
            }
        }

    }

    FSTOOL_COMM_LOG_INFO("%d files, %d free (%d clusters), %d bad (%d clusters)\n",
              stBoot.NumFiles,
              stBoot.NumFree * stBoot.ClusterSize / 1024, stBoot.NumFree,
              stBoot.NumBad * stBoot.ClusterSize / 1024, stBoot.NumBad);

    /****check file system segment******/
    ret |= FSCK_FAT_CheckFragment(u32FragmentThr);
    if (ret & FSCK_FSFATAL)
    {
        FSTOOL_COMM_LOG_ERROR("error during FSCK_FAT_CheckFragment\n");
    }

out:

    FSCK_CACHE_Free(&g_stAllocatedCluster);
    FSCK_FRAGMENT_Free(&g_stFreeCluster);
    FSCK_FRAGMENT_Free(&g_stBadCluster);

    if (needcleandirsection)
    {
        FSCK_DIR_Free();
    }

    if(boot_sector != NULL)
    {
        free(boot_sector);
    }

    if(fsinfo_sector != NULL)
    {
        free(fsinfo_sector);
    }

    if (fat2)
    {
        free(fat2);
    }

    if (fat)
    {
        free(fat);
    }

    FSTOOL_IO_Close(fd);

    return FSTOOL_ConvertErrCode(ret);
}


